/*
 * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.beans.beancontext;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;

import java.beans.VetoableChangeListener;
import java.beans.VetoableChangeSupport;

import java.beans.PropertyVetoException;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * <p>
 * This is a general support class to provide support for implementing the
 * BeanContextChild protocol.
 *
 * This class may either be directly subclassed, or encapsulated and delegated
 * to in order to implement this interface for a given component.
 * </p>
 *
 * @author Laurence P. G. Cable
 * @see java.beans.beancontext.BeanContext
 * @see java.beans.beancontext.BeanContextServices
 * @see java.beans.beancontext.BeanContextChild
 * @since 1.2
 */

public class BeanContextChildSupport implements BeanContextChild, BeanContextServicesListener,
    Serializable {

  static final long serialVersionUID = 6328947014421475877L;

  /**
   * construct a BeanContextChildSupport where this class has been
   * subclassed in order to implement the JavaBean component itself.
   */

  public BeanContextChildSupport() {
    super();

    beanContextChildPeer = this;

    pcSupport = new PropertyChangeSupport(beanContextChildPeer);
    vcSupport = new VetoableChangeSupport(beanContextChildPeer);
  }

  /**
   * construct a BeanContextChildSupport where the JavaBean component
   * itself implements BeanContextChild, and encapsulates this, delegating
   * that interface to this implementation
   *
   * @param bcc the underlying bean context child
   */

  public BeanContextChildSupport(BeanContextChild bcc) {
    super();

    beanContextChildPeer = (bcc != null) ? bcc : this;

    pcSupport = new PropertyChangeSupport(beanContextChildPeer);
    vcSupport = new VetoableChangeSupport(beanContextChildPeer);
  }

  /**
   * Sets the <code>BeanContext</code> for
   * this <code>BeanContextChildSupport</code>.
   *
   * @param bc the new value to be assigned to the <code>BeanContext</code> property
   * @throws PropertyVetoException if the change is rejected
   */
  public synchronized void setBeanContext(BeanContext bc) throws PropertyVetoException {
    if (bc == beanContext) {
      return;
    }

    BeanContext oldValue = beanContext;
    BeanContext newValue = bc;

    if (!rejectedSetBCOnce) {
      if (rejectedSetBCOnce = !validatePendingSetBeanContext(bc)) {
        throw new PropertyVetoException(
            "setBeanContext() change rejected:",
            new PropertyChangeEvent(beanContextChildPeer, "beanContext", oldValue, newValue)
        );
      }

      try {
        fireVetoableChange("beanContext",
            oldValue,
            newValue
        );
      } catch (PropertyVetoException pve) {
        rejectedSetBCOnce = true;

        throw pve; // re-throw
      }
    }

    if (beanContext != null) {
      releaseBeanContextResources();
    }

    beanContext = newValue;
    rejectedSetBCOnce = false;

    firePropertyChange("beanContext",
        oldValue,
        newValue
    );

    if (beanContext != null) {
      initializeBeanContextResources();
    }
  }

  /**
   * Gets the nesting <code>BeanContext</code>
   * for this <code>BeanContextChildSupport</code>.
   *
   * @return the nesting <code>BeanContext</code> for this <code>BeanContextChildSupport</code>.
   */
  public synchronized BeanContext getBeanContext() {
    return beanContext;
  }

  /**
   * Add a PropertyChangeListener for a specific property.
   * The same listener object may be added more than once.  For each
   * property,  the listener will be invoked the number of times it was added
   * for that property.
   * If <code>name</code> or <code>pcl</code> is null, no exception is thrown
   * and no action is taken.
   *
   * @param name The name of the property to listen on
   * @param pcl The <code>PropertyChangeListener</code> to be added
   */
  public void addPropertyChangeListener(String name, PropertyChangeListener pcl) {
    pcSupport.addPropertyChangeListener(name, pcl);
  }

  /**
   * Remove a PropertyChangeListener for a specific property.
   * If <code>pcl</code> was added more than once to the same event
   * source for the specified property, it will be notified one less time
   * after being removed.
   * If <code>name</code> is null, no exception is thrown
   * and no action is taken.
   * If <code>pcl</code> is null, or was never added for the specified
   * property, no exception is thrown and no action is taken.
   *
   * @param name The name of the property that was listened on
   * @param pcl The PropertyChangeListener to be removed
   */
  public void removePropertyChangeListener(String name, PropertyChangeListener pcl) {
    pcSupport.removePropertyChangeListener(name, pcl);
  }

  /**
   * Add a VetoableChangeListener for a specific property.
   * The same listener object may be added more than once.  For each
   * property,  the listener will be invoked the number of times it was added
   * for that property.
   * If <code>name</code> or <code>vcl</code> is null, no exception is thrown
   * and no action is taken.
   *
   * @param name The name of the property to listen on
   * @param vcl The <code>VetoableChangeListener</code> to be added
   */
  public void addVetoableChangeListener(String name, VetoableChangeListener vcl) {
    vcSupport.addVetoableChangeListener(name, vcl);
  }

  /**
   * Removes a <code>VetoableChangeListener</code>.
   * If <code>pcl</code> was added more than once to the same event
   * source for the specified property, it will be notified one less time
   * after being removed.
   * If <code>name</code> is null, no exception is thrown
   * and no action is taken.
   * If <code>vcl</code> is null, or was never added for the specified
   * property, no exception is thrown and no action is taken.
   *
   * @param name The name of the property that was listened on
   * @param vcl The <code>VetoableChangeListener</code> to be removed
   */
  public void removeVetoableChangeListener(String name, VetoableChangeListener vcl) {
    vcSupport.removeVetoableChangeListener(name, vcl);
  }

  /**
   * A service provided by the nesting BeanContext has been revoked.
   *
   * Subclasses may override this method in order to implement their own
   * behaviors.
   *
   * @param bcsre The <code>BeanContextServiceRevokedEvent</code> fired as a result of a service
   * being revoked
   */
  public void serviceRevoked(BeanContextServiceRevokedEvent bcsre) {
  }

  /**
   * A new service is available from the nesting BeanContext.
   *
   * Subclasses may override this method in order to implement their own
   * behaviors
   *
   * @param bcsae The BeanContextServiceAvailableEvent fired as a result of a service becoming
   * available
   */
  public void serviceAvailable(BeanContextServiceAvailableEvent bcsae) {
  }

  /**
   * Gets the <tt>BeanContextChild</tt> associated with this
   * <tt>BeanContextChildSupport</tt>.
   *
   * @return the <tt>BeanContextChild</tt> peer of this class
   */
  public BeanContextChild getBeanContextChildPeer() {
    return beanContextChildPeer;
  }

  /**
   * Reports whether or not this class is a delegate of another.
   *
   * @return true if this class is a delegate of another
   */
  public boolean isDelegated() {
    return !this.equals(beanContextChildPeer);
  }

  /**
   * Report a bound property update to any registered listeners. No event is
   * fired if old and new are equal and non-null.
   *
   * @param name The programmatic name of the property that was changed
   * @param oldValue The old value of the property
   * @param newValue The new value of the property
   */
  public void firePropertyChange(String name, Object oldValue, Object newValue) {
    pcSupport.firePropertyChange(name, oldValue, newValue);
  }

  /**
   * Report a vetoable property update to any registered listeners.
   * If anyone vetos the change, then fire a new event
   * reverting everyone to the old value and then rethrow
   * the PropertyVetoException. <P>
   *
   * No event is fired if old and new are equal and non-null.
   * <P>
   *
   * @param name The programmatic name of the property that is about to change
   * @param oldValue The old value of the property
   * @param newValue - The new value of the property
   * @throws PropertyVetoException if the recipient wishes the property change to be rolled back.
   */
  public void fireVetoableChange(String name, Object oldValue, Object newValue)
      throws PropertyVetoException {
    vcSupport.fireVetoableChange(name, oldValue, newValue);
  }

  /**
   * Called from setBeanContext to validate (or otherwise) the
   * pending change in the nesting BeanContext property value.
   * Returning false will cause setBeanContext to throw
   * PropertyVetoException.
   *
   * @param newValue the new value that has been requested for the BeanContext property
   * @return <code>true</code> if the change operation is to be vetoed
   */
  public boolean validatePendingSetBeanContext(BeanContext newValue) {
    return true;
  }

  /**
   * This method may be overridden by subclasses to provide their own
   * release behaviors. When invoked any resources held by this instance
   * obtained from its current BeanContext property should be released
   * since the object is no longer nested within that BeanContext.
   */

  protected void releaseBeanContextResources() {
    // do nothing
  }

  /**
   * This method may be overridden by subclasses to provide their own
   * initialization behaviors. When invoked any resources required by the
   * BeanContextChild should be obtained from the current BeanContext.
   */

  protected void initializeBeanContextResources() {
    // do nothing
  }

  /**
   * Write the persistence state of the object.
   */

  private void writeObject(ObjectOutputStream oos) throws IOException {

        /*
         * don't serialize if we are delegated and the delegator is not also
         * serializable.
         */

    if (!equals(beanContextChildPeer) && !(beanContextChildPeer instanceof Serializable)) {
      throw new IOException("BeanContextChildSupport beanContextChildPeer not Serializable");
    } else {
      oos.defaultWriteObject();
    }

  }


  /**
   * Restore a persistent object, must wait for subsequent setBeanContext()
   * to fully restore any resources obtained from the new nesting
   * BeanContext
   */

  private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ois.defaultReadObject();
  }

    /*
     * fields
     */

  /**
   * The <code>BeanContext</code> in which
   * this <code>BeanContextChild</code> is nested.
   */
  public BeanContextChild beanContextChildPeer;

  /**
   * The <tt>PropertyChangeSupport</tt> associated with this
   * <tt>BeanContextChildSupport</tt>.
   */
  protected PropertyChangeSupport pcSupport;

  /**
   * The <tt>VetoableChangeSupport</tt> associated with this
   * <tt>BeanContextChildSupport</tt>.
   */
  protected VetoableChangeSupport vcSupport;

  /**
   * The bean context.
   */
  protected transient BeanContext beanContext;

  /**
   * A flag indicating that there has been
   * at least one <code>PropertyChangeVetoException</code>
   * thrown for the attempted setBeanContext operation.
   */
  protected transient boolean rejectedSetBCOnce;

}
